feat(bulma): @paper/bulma — Bulma compat design system (Tier 1) - #760
Open
johnleider wants to merge 19 commits into
Open
feat(bulma): @paper/bulma — Bulma compat design system (Tier 1)#760johnleider wants to merge 19 commits into
johnleider wants to merge 19 commits into
Conversation
Components render real Bulma 1.0.4 markup and classes against the user's own bulma.css while @vuetify/v0 supplies behavior, focus management, and accessibility. Bulma ships zero JavaScript by design; this package is the JS its docs tell you to bring. - 22 Tier-1 components (Bu* prefix): modal, dropdown, tabs, navbar, message, notification, pagination, breadcrumb, panel, menu, and the full form set (field/control/label/help/input/textarea/select/ checkbox/radio/file) - Conformance harness: fixtures captured from bulma.io's documented markup, DOM/class diffing with a strict additive-attr tolerance charter, plus an axe-core a11y sweep over every component - SPEC.md declares the compat-class deviations from DESIGN_SYSTEMS.md rulings and known Tier-1 limitations, each with its v0-core follow-up - No stylesheet artifact and no owned token namespace: bulma@^1.0 is an optional peer; the user's CSS is the styling source
BuInput's internal root is renderless and subtree-scoped, so a sibling BuHelp silently renders an empty .help while the input still shows is-danger — deceptively half-wired. Dev-only watchEffect warning plus a SPEC.md known-limitation entry documenting the InputRoot composition.
No named slots in the package: every named region is now an express part component named after Bulma's own part class, composed in userland exactly as the markup nests, reading parent state through provide/inject. New parts: BuModalCard, BuModalContent, BuModalHead, BuModalTitle, BuModalBody, BuModalFoot, BuModalClose, BuMessageHeader, BuMessageBody, BuDropdownTrigger, BuDropdownMenu, BuNavbarBrand, BuNavbarMenu, BuTabList, BuFieldLabel, BuFieldBody, BuPanelHeading, BuPanelTabs, BuPanelTab, BuPanelBlock, BuPanelIcon. Text-only slots become props (BuPagination previous/next, BuFile icon); BuField's label slot is deleted in favour of composing BuLabel.
Landing page + BuModal/BuDropdown pages under a new Design Systems nav section (clickable parent node, Guide-style). Examples render inside iframed sandboxes (separate chrome-less vite entry) so Bulma's global CSS stays isolated from the docs chrome: frames follow the docs theme live via postMessage, and overlay components promote the frame to a transparent full-viewport layer while pinning the inline example box in place (0px trigger drift), restoring on close. Each page pairs the verbatim conformance fixture markup with the component usage that renders it, and links the upstream bulma.io documentation.
Recipes promoted into four iframed examples (hoverable, alignment, menu content, arbitrary content) each covering one concept; Accessibility gains a Dismissal table and the subtree-bound Escape rationale; role=menu policy gets its own Menu semantics section. Sandbox now measures on pointer events so CSS-only hover reveals resize their frame.
…motion Generalizes promotion from position:fixed detection to any floating content escaping the inline box. Iframes paint an opaque canvas even with transparent documents, so the promoted frame is clipped via clip-path to exactly the regions the sandbox reports painting (pinned box + escaping surfaces) — this also fixes the committed modal path, whose backdrop was dimming the frame's white canvas rather than the page. Clipped regions aren't hit-testable, so the parent replays outside pointerdowns for v0's click-outside; the pin re-syncs on scroll while promoted. Promotion engages only when content actually escapes (the alignment example's is-up menu fits its box and never promotes); hover promotion proved flicker-free with deferred pointer reads and an in-flight gate.
… promote The ancestor z-index lift was tracked per example instance while lifting the same shared ancestors, so overlapping promotions restored each other's lifted values and left page content permanently raised. Promotion is now a page-level singleton: one owner token, one shared lift record, one release() used by close, displacement, and teardown; a second promoter displaces the first with full restore. Hover-revealed content latches to frame growth for the whole reveal — hover promotion churned the shared lift and the growth resize re-promoted on rebound.
…y covers it The sandbox classifies each promotion: overlay (viewport-laid-out surface — modal + backdrop) lifts ancestor stacking contexts to cover chrome as before; float (in-flow content spilling out — dropdown menus) never lifts, so the sticky app bar occludes it on scroll exactly like surrounding page content. Pin, clip, singleton ownership, dismissal bridge, and scroll re-sync stay shared; a float escalates to overlay on the next rects message if a modal opens within it.
…gions Region rects were measured before pin() ran, so they described the sandbox's frame-local layout and the parent clipped the promoted frame to a box at the viewport corner — the visible flash, intermittent only by how many frames it survived. Order is now pin, re-measure, post; promotion is refused when no box rect is cached (falls back to growth). The close path had the mirror bug (unpin before demote) — it now holds the pin until the parent's demotion ack, with a 300ms safety. Verified frame-granular: 1,300+ sampled promoted frames across both tiers and themes at 1x and 4x CPU throttle, zero flashed or unclipped frames.
…ristics The hover latch raced the observers — whichever signal classified a reveal first won, so is-hoverable menus nondeterministically grew or promoted. Classification is now a fact read fresh at measurement time: viewport-laid-out surface → overlay; escaping content owned by an is-active ancestor (component-set state only) → float; anything else, including pure-CSS :hover reveals → grow. The pointer listener remains solely as a change-detector (CSS reveals fire no observer); the growing latch and hover-threading are deleted. Also fixes the growth path reading the now-empty promotion rects instead of the escaping surfaces.
Reuses Bulma's own markup — .field.has-addons > .control > button.button / input.input — so the composite needs zero custom CSS; verified in-browser that upstream's addon radii, flex, and is-expanded rules engage on it unchanged. Ships the first SELF-AUTHORED fixture (Bulma documents no number input): its header states the provenance and traces every class to another fixture block or a bulma.css selector, so only the arrangement is ours and drift still fails. Parts are BuNumberFieldDecrement / BuNumberFieldInput / BuNumberFieldIncrement, each rendering its own structural .control.
First composite (gap-filler) page: no upstream bulma.io component page exists, so 'The markup you know' becomes 'The markup it composes' — the Bulma tab shows the self-authored fixture whose provenance traces every class to an upstream fixture block or bulma.css selector, prose states what conformance can and cannot claim for a composed contract, and the upstream link targets the has-addons form docs the composition derives from. Index Components table gains a 'None — composed' taxonomy row.
…boxes White flash on dark theme: the sandbox HTML head now sets data-theme from the query synchronously with critical background colors inline (no dependence on CDN CSS or the app bundle), and the parent aligns the iframe's color-scheme with the docs theme — a scheme mismatch forces an opaque white canvas even before load. Height jumps: measurements posted before the sandbox's ready handshake (the empty-document size) are ignored; the placeholder reserves the last-measured height per example from sessionStorage so warm loads have zero shifts and cold loads settle once. The reserve yields to every post-ready measurement in both directions, and each applied measurement refreshes the cache.
Keep Bulma iframe sandbox for CSS-reset systems; Emerald stays inline.
Fill remaining Bu* pages (Composed on v0, Reference, examples). Systems previews: light/dark only, shared code-group expand, iframe first-paint pulse, no-resize landing example, glass-surface safelist, AppNav hide transform without ltr:.
Contributor
|
✅ Changeset found — this change will be included in the next release. Thanks! |
johnleider
marked this pull request as ready for review
August 21, 2026 21:39
`!v0:browser` still matched `bulma:browser`, so the unit job launched Chromium without installing it. Exclude every `*:browser` project from unit and run them all in the browser job.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
First compat-class design system: components render real Bulma 1.0.4 markup and classes against the user's own
bulma.css, while@vuetify/v0supplies all behavior, focus management, and accessibility. Bulma ships zero JavaScript by design — its docs tell you to bring your own JS for modal, dropdown, navbar burger, and dismissals. This package is that JS.Sibling effort: J-Sek/v0-daisyui (daisyfy). The compat-class addendum to
DESIGN_SYSTEMS.mdis deliberately deferred — it gets extracted from both packages once Tier 1 experience settles.Contents
Bu*prefix;B*reserved for a future Bootstrap compat): BuModal (content + card variants, hand-rolled focus trap), BuDropdown, BuTabs/BuTab/BuTabPanel, BuNavbar, BuMessage, BuNotification, BuPagination, BuBreadcrumb, BuPanel, BuMenu, and the form set — BuField/BuControl/BuLabel/BuHelp/BuInput/BuTextarea/BuSelect/BuCheckbox/BuRadio/BuFile.harness/): fixtures captured from bulma.io's documented markup;conform()diffs rendered DOM structure, classes, attributes, and text against them with a strict tolerance charter (only additivearia-*/data-*/role/tabindex/idallowed). Built repo-generic so later compat targets reuse it.packages/0's sweep.DESIGN_SYSTEMS.mdrulings (no owned token namespace; upstreamis-*state classes; native form controls where Bulma's CSS demands them) and Known Limitations, each with its v0-core follow-up.v0-core follow-ups surfaced (to be filed as issues)
useFocusTrapcomposable (BuModal hand-rolls containment)DialogContentstack ticket can't opt out of scrim (global<Scrim>double-backdrops)BreadcrumbsRoothides overflow items even with no Ellipsis registeredTabsItemrenderless mode registers no element → focus-follow no-opInputControlmergeProps assignsundefinedkeys (wrapper can't forcereadonly); exportparseValidateOnfrom the Input barrelNotes
bulma@^1.0is an optional peer — zero-config consumers load Bulma off-CDN. No stylesheet artifact ships.bulma:browserproject); package + root typecheck, lint, knip/sherif clean.@paper/*first publish requires a manual scope bootstrap; versioning starts at 0.1.0 on that event.Spec approved 2026-07-31 (compat direction,
@paper/bulmain-monorepo,Bu*prefix).Docs (added after review rounds)
A Design Systems docs section with the Bulma pilot: landing + BuModal/BuDropdown pages. Examples run in iframed sandboxes so Bulma's global CSS never touches the docs chrome — frames follow the docs theme live, and modals promote the frame to a transparent full-viewport layer (inline example box pinned in place) so overlays cover the page. Each page pairs the verbatim conformance fixture with the component usage that renders it and links the upstream bulma.io docs. Region slots were also reworked into express part components (
BuModalHead/BuModalTitle/…, no named slots) per review.